package com.niodata.dp.core.usermgr.op.impl;

import com.niodata.dp.core.OwnerType;
import com.niodata.dp.core.exception.ErrorCodes;
import com.niodata.dp.core.hdfs.HdfsFileSystem;
import com.niodata.dp.core.resource.AbstractResourceDetailResolver;
import com.niodata.dp.core.resource.ResourceDetail;
import com.niodata.dp.core.resource.ResourceMapper;
import com.niodata.dp.core.resource.ResourceType;
import com.niodata.dp.core.security.ServiceSecurityManager;
import com.niodata.dp.core.service.SystemServices;
import com.niodata.dp.core.usermgr.DpUsermgrException;
import com.niodata.dp.core.usermgr.HdfsUtil;
import com.niodata.dp.core.usermgr.dao.GroupInfoMapper;
import com.niodata.dp.core.usermgr.dao.UserGroupMapper;
import com.niodata.dp.core.usermgr.dao.UserInfoMapper;
import com.niodata.dp.core.usermgr.dao.UserKeytabMapper;
import com.niodata.dp.core.usermgr.enums.AuthType;
import com.niodata.dp.core.usermgr.enums.GroupRole;
import com.niodata.dp.core.usermgr.model.GroupInfo;
import com.niodata.dp.core.usermgr.model.GroupProfile;
import com.niodata.dp.core.usermgr.model.UserGroup;
import com.niodata.dp.core.usermgr.model.UserGroupInfo;
import com.niodata.dp.core.usermgr.model.UserInfo;
import com.niodata.dp.core.usermgr.model.UserKeytab;
import com.niodata.dp.core.usermgr.model.result.GroupResult;
import com.niodata.dp.core.usermgr.op.GroupInfoService;
import com.niodata.dp.core.usermgr.op.kl.KdcLdapOperation;
import com.niodata.dp.log.LogUtil;
import com.niodata.dp.util.IdUtil;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("DpUserMgrGroup")
public class GroupInfoServiceImpl extends AbstractResourceDetailResolver
      implements GroupInfoService {

  private static final Logger logger = Logger.getLogger(GroupInfoServiceImpl.class);
  //@Autowired
  //private SystemManagerService systemManagerService;
  private final String defaultTargets = "*";
  private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
  @Autowired
  @Qualifier("ServiceSecurityManagerImpl")
  ServiceSecurityManager serviceSecurityManager;
  @Autowired
  private GroupInfoMapper groupInfoMapper;
  @Autowired
  private UserGroupMapper userGroupMapper;
  @Autowired
  private UserKeytabMapper userKeytabMapper;
  @Autowired
  private UserInfoMapper userInfoMapper;
  @Autowired
  private ResourceMapper resourceMapper;

  @Override
  @Transactional
  public long createGroup(GroupInfo groupInfo, Long ownerId, boolean mapToHdfs)
        throws DpUsermgrException {
    // Insert GroupInf
    groupInfo.setCreateTime(new Timestamp(System.currentTimeMillis()));
    groupInfo.setHdfsHome(HdfsUtil.getGroupHdfsHome(groupInfo.getGroupName()));
    groupInfo.setReaderUser(HdfsUtil.getHdfsGroupReadUser(groupInfo.getGroupName()));
    //groupInfo.setHdfsUser(HdfsUtil.getHdfsGroupOwnerUser(groupInfo.getGroupName()));
    groupInfo.setHdfsUser(groupInfo.getGroupName());
    groupInfo.setHdfsGroupName(HdfsUtil.getHdfsGroupName(groupInfo.getGroupName()));
    groupInfo.setActive(true);
    groupInfoMapper.insert(groupInfo);
    // Add relation to UserGroup
    UserGroup userGroup = new UserGroup();
    userGroup.setUserId(ownerId);
    userGroup.setGroupId(groupInfo.getGroupId());
    userGroup.setRole(GroupRole.OWNER);
    userGroup.setCreateTime(new Timestamp(System.currentTimeMillis()));
    userGroupMapper.insert(userGroup);
    if (!mapToHdfs) {
      return userGroup.getGroupId();
    }
    ResourceDetail resourceDetail = new ResourceDetail();
    resourceDetail.setOwnerId(groupInfo.getGroupId());
    resourceDetail.setOwnerType(OwnerType.GROUP);
    resourceDetail.setPath(groupInfo.getHdfsHome());
    resourceDetail.setTypeName(ResourceType.File.getTypeName());
    resourceDetail.setCreateTime(new Date());
    resourceDetail.setId(IdUtil.uuid());
    resourceMapper.saveResource(resourceDetail);

    if (HdfsFileSystem.getInstance().exist(groupInfo.getHdfsHome())) {
      throw new DpUsermgrException(ErrorCodes.ERROR_HDFS, "group folder exists");
    }
    // Create groupMasterName and groupReaderName
    logger.info("use group info hdfs user:" + groupInfo.getHdfsUser());
    logger.info("use group info hdfs reader user:" + groupInfo.getReaderUser());
    //Todo remove group owner mapping
    String defaultPassword = passwordEncoder.encode("123456");
    //getKeytabAndInsertUserKeytab(groupInfo.getHdfsUser(), defaultPassword);
    getKeytabAndInsertUserKeytab(groupInfo.getReaderUser(), defaultPassword);
    // LDAP中创建组
    String groupName = groupInfo.getHdfsGroupName();
    KdcLdapOperation.getInstance().createGroup(groupName, groupInfo.getHdfsUser());
    UserInfo userInfo = userInfoMapper.getUserInfo(ownerId);
    KdcLdapOperation.getInstance().addUserToGroup(groupInfo.getReaderUser(), groupName);
    //KdcLdapService.getInstance().addUserToGroup(userInfo.getHdfsUser(), groupName);
    // Create HDFS home directory and authorize
    HdfsFileSystem.getInstance().mkdir(groupInfo.getHdfsHome(), groupInfo.getHdfsUser(), groupName);
    //systemManagerService.addGroup(groupName, defaultTargets);
    return userGroup.getGroupId();
  }


  @Override
  @Transactional
  public void removeGroup(Long groupId) throws DpUsermgrException {
    groupInfoMapper.updateStatus(groupId, 0);
    GroupInfo groupInfo = groupInfoMapper.getGroupInfo(groupId);
    //systemManagerService.deleteGroup(groupInfo.getGroupName(), defaultTargets);
  }

  @Override
  @Transactional
  public void resetGroupOwner(Long groupId, Long newOwnerUserId) throws DpUsermgrException {
    UserInfo owner = this.groupInfoMapper.getGroupOwner(groupId);
    UserGroup userGroup = userGroupMapper.getUserGroup(owner.getUserId(), groupId);
    userGroup.setRole(GroupRole.MASTER);
    userGroupMapper.updateUserGroup(userGroup);
    UserGroup newOwnerUg = this.userGroupMapper.getUserGroup(newOwnerUserId, groupId);
    if (newOwnerUg == null) {
      newOwnerUg = new UserGroup();
      newOwnerUg.setRole(GroupRole.OWNER);
      newOwnerUg.setCreateTime(new Timestamp(new Date().getTime()));
      newOwnerUg.setGroupId(groupId);
      newOwnerUg.setUserId(newOwnerUserId);
      userGroupMapper.insert(newOwnerUg);
    } else {
      newOwnerUg.setRole(GroupRole.OWNER);
      userGroupMapper.updateUserGroup(newOwnerUg);
    }
  }

  @Override
  @Transactional
  public void addUserToGroup(Long groupId, Long userId, GroupRole role)
        throws DpUsermgrException {
    UserGroup userGroup = new UserGroup();
    userGroup.setCreateTime(new Timestamp(new Date().getTime()));
    userGroup.setUserId(userId);
    userGroup.setGroupId(groupId);
    userGroup.setRole(role);
    userGroup.setCreateTime(new Timestamp(System.currentTimeMillis()));
    userGroupMapper.insert(userGroup);
    UserInfo userInfo = userInfoMapper.getUserInfo(userId);
    GroupInfo groupInfo = groupInfoMapper.getGroupInfo(groupId);
    String groupName = groupInfo.getHdfsGroupName();
    KdcLdapOperation.getInstance().addUserToGroup(userInfo.getHdfsUser(), groupName);
    //systemManagerService
    //    .groupAddUser(userInfo.getLoginName(), groupInfo.getGroupName(), defaultTargets);
  }

  @Override
  @Transactional
  public void addUserToGroup(List<Long> userIds, Long groupId, GroupRole role)
        throws DpUsermgrException {
    if (role.equals(GroupRole.OWNER)) {
      throw new DpUsermgrException(ErrorCodes.ERROR_INTERNAL, "group owner should be one");
    }
    GroupInfo groupInfo = groupInfoMapper.getGroupInfo(groupId);
    for (Long userId : userIds) {
      UserInfo userInfo = userInfoMapper.getUserInfo(userId);
      String groupName = groupInfo.getHdfsGroupName();
      KdcLdapOperation.getInstance().addUserToGroup(userInfo.getHdfsUser(), groupName);
      UserGroup userGroup = new UserGroup();
      userGroup.setUserId(userId);
      userGroup.setCreateTime(new Timestamp(System.currentTimeMillis()));
      userGroup.setGroupId(groupId);
      userGroup.setRole(role);
      userGroup.setCreateTime(new Timestamp(new Date().getTime()));
      userGroupMapper.insert(userGroup);
      //systemManagerService
      //    .groupAddUser(userInfo.getLoginName(), groupInfo.getGroupName(), defaultTargets);
    }
  }

  @Override
  public void removeUserFromGroup(Long groupId, Long userId) throws DpUsermgrException {
    UserGroup userGroup = userGroupMapper.getUserGroup(groupId, userId);
    if (userGroup == null) {
      return;
    }
    if (userGroup.getRole().equals(GroupRole.OWNER)) {
      throw new DpUsermgrException(ErrorCodes.ERROR_INTERNAL,
            "owner can not be removed from group");
    }
    GroupInfo groupInfo = groupInfoMapper.getGroupInfo(groupId);
    String groupName = groupInfo.getHdfsGroupName();
    UserInfo userInfo = userInfoMapper.getUserInfo(userId);
    KdcLdapOperation.getInstance().removeUserFromGroup(userInfo.getHdfsUser(), groupName);
    userGroupMapper.delete(userId, groupId);
    userGroupMapper.delete(userId, groupId);
    //systemManagerService
    //    .groupDelUser(userInfo.getLoginName(), groupInfo.getGroupName(), defaultTargets);

  }

  @Override
  @Transactional
  public void removeUserFromGroup(List<Long> userIds, Long groupId) throws DpUsermgrException {
    GroupInfo groupInfo = groupInfoMapper.getGroupInfo(groupId);
    String groupName = groupInfo.getHdfsGroupName();
    for (Long userId : userIds) {
      UserInfo userInfo = userInfoMapper.getUserInfo(userId);
      KdcLdapOperation.getInstance().removeUserFromGroup(userInfo.getHdfsUser(), groupName);
      userGroupMapper.delete(userId, groupId);
      //systemManagerService
      //    .groupDelUser(userInfo.getLoginName(), groupInfo.getGroupName(), defaultTargets);

    }
  }

  @Override
  public UserInfo getGroupOwner(Long groupId) throws DpUsermgrException {
    return groupInfoMapper.getGroupOwner(groupId);
  }

  @Override
  @Transactional
  public void updateGroupInfo(GroupInfo groupInfo) throws DpUsermgrException {
    groupInfoMapper.update(groupInfo);
  }

  @Override
  public GroupInfo getGroupInfo(Long groupId) throws DpUsermgrException {
    return groupInfoMapper.getGroupInfo(groupId);
  }

  @Override
  public GroupInfo getGroupInfo(String groupName) throws DpUsermgrException {
    return groupInfoMapper.getGroupInfoByName(groupName);
  }

  @Override
  public int getGroupCount() throws DpUsermgrException {
    return this.groupInfoMapper.getGroupInfoCount();
  }

  @Override
  public boolean checkUserInGroup(Long userId, Long groupId) throws DpUsermgrException {
    return userGroupMapper.getUserGroup(userId, groupId) != null;
  }

  @Override
  public List<GroupResult> getAllGroups(int pageIndex, int pageSize) throws DpUsermgrException {
    List<GroupInfo> list = groupInfoMapper.getGroupInfoList((pageIndex - 1) * pageSize, pageSize);
    List<GroupResult> ret = new ArrayList<>();
    for (GroupInfo groupInfo : list) {
      UserInfo owner = userInfoMapper.getGroupOwner(groupInfo.getGroupId());
      GroupResult groupResult = new GroupResult();
      groupResult.setGroupId(groupInfo.getGroupId());
      groupResult.setGroupName(groupInfo.getGroupName());
      groupResult.setHdfsHome(groupInfo.getHdfsHome());
      groupResult.setActive(groupInfo.isActive());
      groupResult.setDetail(groupInfo.getDetail());
      groupResult.setOwner(owner);
      groupResult
            .setCreateBucket(serviceSecurityManager.isGroupGrantedService(groupInfo.getGroupId(),
                  SystemServices.getSystemService(ResourceType.Object.getTypeName())));
      groupResult
            .setCreateDatabase(serviceSecurityManager.isGroupGrantedService(groupInfo.getGroupId(),
                  SystemServices.getSystemService(ResourceType.Db.getTypeName())));
      groupResult
            .setSubmitPackage(serviceSecurityManager.isGroupGrantedService(groupInfo.getGroupId(),
                  SystemServices.getSystemService(ResourceType.Plugin.getTypeName())));
      groupResult
            .setSubmitWorkflow(serviceSecurityManager.isGroupGrantedService(groupInfo.getGroupId(),
                  SystemServices.getSystemService(ResourceType.Job.getTypeName())));
      groupResult.setNoteBook(serviceSecurityManager.isGroupGrantedService(groupInfo.getGroupId(),
            SystemServices.getSystemService(ResourceType.NoteBook.getTypeName())));
      groupResult.setDiRes(serviceSecurityManager.isGroupGrantedService(groupInfo.getGroupId(),
            SystemServices.getSystemService(ResourceType.DataEntityInterface.getTypeName())));
      ret.add(groupResult);
    }
    return ret;
  }

  @Override
  @Transactional
  public List<GroupInfo> getAllGroups(Long userId) throws DpUsermgrException {
    List<UserGroup> userGroupList = getUserGroups(userId);
    return getAllGroups(userGroupList);
  }

  @Override
  @Transactional
  public List<GroupInfo> getAllGroups(List<UserGroup> userGroupList) throws DpUsermgrException {
    List<GroupInfo> groupInfoList = new ArrayList<>();
    for (UserGroup userGroup : userGroupList) {
      GroupInfo groupInfo = groupInfoMapper.getGroupInfo(userGroup.getGroupId());
      groupInfoList.add(groupInfo);
    }
    return groupInfoList;
  }

  @Override
  @Transactional
  public List<UserGroup> getUserGroups(Long userId) {
    return userGroupMapper.getGroupRoleList(userId, true);
  }

  @Override
  public List<UserGroupInfo> getUserGroupInfoOfGroup(Long groupId) {
    return this.userGroupMapper.getUserGroupInfoOfGroup(groupId);
  }


  @Override
  @Transactional
  public boolean isOwner(Long userId, Long groupId) throws DpUsermgrException {
    UserGroup userGroup = userGroupMapper.getUserGroup(userId, groupId);
    return userGroup != null && userGroup.getRole().equals(GroupRole.OWNER);
  }

  @Override
  public UserGroup getUserGroupInfo(Long userId, Long groupId) throws DpUsermgrException {
    UserGroup userGroup = userGroupMapper.getUserGroup(userId, groupId);
    return userGroup;
  }

  @Override
  public boolean isMasterOrOwner(Long userId, Long groupId) {
    UserGroup userGroup = this.getUserGroupInfo(userId, groupId);
    return userGroup != null && (userGroup.getRole().name().equals(GroupRole.MASTER.name())
          || userGroup.getRole().name().equals(GroupRole.OWNER.name()));
  }

  @Override
  public GroupInfo resolveResource(AuthType authType, String authPath)
        throws DpUsermgrException {
    return this.groupInfoMapper.resolveResource(authType, getPathHome(authPath));
  }

  @Override
  public List<GroupInfo> searchGroupByName(String nameKeyWord) {
    return groupInfoMapper.searchGroupByName(nameKeyWord);
  }

  @Override
  public void createOrUpdateProfile(long groupId, GroupProfile profile) {
    GroupInfo groupInfo = this.groupInfoMapper.getGroupInfo(groupId);
    List<GroupProfile> profiles = groupInfo.getProfiles();
    if (profiles == null) {
      profiles = Collections.emptyList();
    }
    profile.setGroupId(groupId);
    boolean update = false;
    for (GroupProfile p : profiles) {
      if (p.getType().equals(profile.getType())) {
        update = true;
      }
    }

    if (update) {
      groupInfoMapper.updateGroupProfile(profile);
    } else {
      groupInfoMapper.insertGroupProfile(profile);
    }
  }

  public ResourceDetail doResolve(ResourceType resourceType, String groupIdStr) {
    long groupId = Long.parseLong(groupIdStr);
    ResourceDetail detail = new ResourceDetail();
    detail.setOwnerId(groupId);
    detail.setResourceType(ResourceType.Group);
    return detail;
  }


  public boolean supportResourceType(Class<? extends ResourceType> clazz) {
    return ResourceType.Group.getClass().equals(clazz);
  }

  private String getPathHome(String path) {
    if (path == null || path.isEmpty()) {
      return "";
    }
    if (path.startsWith("/")) {
      path = path.substring(1);
    }
    return path.substring(0, !path.contains("/") ? path.length() : path.indexOf("/"));
  }

  @Transactional
  private void getKeytabAndInsertUserKeytab(String hdfsUserName, String encodedPassword) {
    byte[] keytab;
    try {
      keytab = KdcLdapOperation.getInstance()
            .createUserAndReturnKeyTab(hdfsUserName, encodedPassword);
    } catch (DpUsermgrException ex) {
      LogUtil.logStackTrace(logger, ex);
      logger.error("create keytab failed from hdfs user:" + hdfsUserName);
      throw new DpUsermgrException(ErrorCodes.ERROR_KDC, "Create keytab failed.");
    }
    UserKeytab userKeytab = new UserKeytab();
    userKeytab.setUserName(hdfsUserName);
    userKeytab.setKeytab(keytab);
    userKeytabMapper.insert(userKeytab);
  }

  @Override
  public List<GroupResult> getOwnerGroups(Long userId) throws DpUsermgrException {
    UserInfo owner = userInfoMapper.getUserInfo(userId);
    List<GroupInfo> list = groupInfoMapper.getUserGroupInfoList(userId, GroupRole.OWNER);
    List<GroupResult> ret = new ArrayList<>();
    for (GroupInfo groupInfo : list) {
      GroupResult groupResult = new GroupResult();
      groupResult.setOwner(owner);
      groupResult.setGroupId(groupInfo.getGroupId());
      groupResult.setGroupName(groupInfo.getGroupName());
      groupResult.setDetail(groupInfo.getDetail());
      ret.add(groupResult);
    }
    return ret;
  }

  @Override
  public List<GroupResult> getOwnerOrMasterGroups(Long userId) throws DpUsermgrException {
    UserInfo owner = userInfoMapper.getUserInfo(userId);
    List<GroupInfo> list = groupInfoMapper.getUserGroupInfoList(userId, GroupRole.MASTER);
    List<GroupInfo> list2 = groupInfoMapper.getUserGroupInfoList(userId, GroupRole.OWNER);
    list.addAll(list2);
    List<GroupResult> ret = new ArrayList<>();
    for (GroupInfo groupInfo : list) {
      GroupResult groupResult = new GroupResult();
      groupResult.setOwner(owner);
      groupResult.setGroupId(groupInfo.getGroupId());
      groupResult.setGroupName(groupInfo.getGroupName());
      groupResult.setDetail(groupInfo.getDetail());
      ret.add(groupResult);
    }
    return ret;
  }

}
